/*******************************************************************************
 * Copyright (C) Marvell International Ltd. and its affiliates
 *
 * This software file (the "File") is owned and distributed by Marvell
 * International Ltd. and/or its affiliates ("Marvell") under the following
 * alternative licensing terms.  Once you have made an election to distribute the
 * File under one of the following license alternatives, please (i) delete this
 * introductory statement regarding license alternatives, (ii) delete the three
 * license alternatives that you have not elected to use and (iii) preserve the
 * Marvell copyright notice above.
 *
 ********************************************************************************
 * Marvell Commercial License Option
 *
 * If you received this File from Marvell and you have entered into a commercial
 * license agreement (a "Commercial License") with Marvell, the File is licensed
 * to you under the terms of the applicable Commercial License.
 *
 ********************************************************************************
 * Marvell GPL License Option
 *
 * If you received this File from Marvell, you may opt to use, redistribute and/or
 * modify this File in accordance with the terms and conditions of the General
 * Public License Version 2, June 1991 (the "GPL License"), a copy of which is
 * available along with the File in the license.txt file or by writing to the Free
 * Software Foundation, Inc., or on the worldwide web at http://www.gnu.org/licenses/gpl.txt.
 *
 * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE ARE EXPRESSLY
 * DISCLAIMED.  The GPL License provides additional details about this warranty
 * disclaimer.
 *
 ********************************************************************************
 * Marvell GNU General Public License FreeRTOS Exception
 *
 * If you received this File from Marvell, you may opt to use, redistribute and/or
 * modify this File in accordance with the terms and conditions of the Lesser
 * General Public License Version 2.1 plus the following FreeRTOS exception.
 * An independent module is a module which is not derived from or based on
 * FreeRTOS.
 * Clause 1:
 * Linking FreeRTOS statically or dynamically with other modules is making a
 * combined work based on FreeRTOS. Thus, the terms and conditions of the GNU
 * General Public License cover the whole combination.
 * As a special exception, the copyright holder of FreeRTOS gives you permission
 * to link FreeRTOS with independent modules that communicate with FreeRTOS solely
 * through the FreeRTOS API interface, regardless of the license terms of these
 * independent modules, and to copy and distribute the resulting combined work
 * under terms of your choice, provided that:
 * 1. Every copy of the combined work is accompanied by a written statement that
 * details to the recipient the version of FreeRTOS used and an offer by yourself
 * to provide the FreeRTOS source code (including any modifications you may have
 * made) should the recipient request it.
 * 2. The combined work is not itself an RTOS, scheduler, kernel or related
 * product.
 * 3. The independent modules add significant and primary functionality to
 * FreeRTOS and do not merely extend the existing functionality already present in
 * FreeRTOS.
 * Clause 2:
 * FreeRTOS may not be used for any competitive or comparative purpose, including
 * the publication of any form of run time or compile time metric, without the
 * express permission of Real Time Engineers Ltd. (this is the norm within the
 * industry and is intended to ensure information accuracy).
 *
 ********************************************************************************
 * Marvell BSD License Option
 *
 * If you received this File from Marvell, you may opt to use, redistribute and/or
 * modify this File under the following licensing terms.
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *	* Redistributions of source code must retain the above copyright notice,
 *	  this list of conditions and the following disclaimer.
 *
 *	* Redistributions in binary form must reproduce the above copyright
 *	  notice, this list of conditions and the following disclaimer in the
 *	  documentation and/or other materials provided with the distribution.
 *
 *	* Neither the name of Marvell nor the names of its contributors may be
 *	  used to endorse or promote products derived from this software without
 *	  specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *******************************************************************************/

#define log_fmt(fmt, ...) "dev_mng: " fmt, ##__VA_ARGS__

#include "std_internal.h"
#include "env/trace/trc_pf.h"
#include "drivers/mv_mqa.h"
#include "drivers/mv_giu.h"
#include "db.h"
#include "lf/lf_mng.h"
#include "lf/pf/pf.h"
#include "lf/pf/pf_topology.h"
#include "lf/custom/custom.h"
#include "lf/mng_cmd_desc.h"
#include "mng/mv_nmp_dispatch.h"
#include "lib/lib_misc.h"

#include "dev_mng.h"
#include "dev_mng_pp2.h"
#include "pci_ep_def.h"
#include "dispatch.h"


#define NMP_MSI_MAP_REGS_INDX	"0"

			     /*   Armada 7/8K        Armada 8K+  */
static char *nmp_msi_map_names[] = {"arm,gic-v2m-frame", "arm,gic-v3-its"};
static u8 nmp_msi_map_indxs[] = {0, 0};
static u16 nmp_msi_map_regs_offs[] = {0x40, 0x40};


/** =========================== **/
/** == Device Initialization == **/
/** =========================== **/

/** Hardware Functionality **/
/** ====================== **/

static int dev_mng_alloc_emul_pci_bars(struct nmp *nmp)
{
	struct mv_sys_dma_mem_info	 mem_info;
	char				 mem_name[100];

	/* Allocate configuration space memory */
	BUILD_BUG_ON(PCI_BAR0_CALC_SIZE > PCI_BAR0_ALLOC_SIZE); /* check that allocated size is enough */

	/* Allocate configuration space memory */
	BUILD_BUG_ON(PCI_BAR0_CALC_SIZE > PCI_BAR0_ALLOC_SIZE); /* check that allocated size is enough */

	nmp->emul_bars_avail_tbl = kcalloc(NMP_PCI_MAX_NUM_BARS, sizeof(int), GFP_KERNEL);
	if (nmp->emul_bars_avail_tbl == NULL)
		return -ENOMEM;
	memset(nmp->emul_bars_avail_tbl, 0, NMP_PCI_MAX_NUM_BARS * sizeof(int));

	nmp->emul_bars_mem = mv_sys_dma_mem_alloc(NMP_PCI_MAX_NUM_BARS * PCI_BAR0_ALLOC_SIZE, PCI_BAR0_ALLOC_ALIGN);
	if (nmp->emul_bars_mem == NULL) {
		pr_err("Failed to allocate platform configuration space.\n");
		return -ENOMEM;
	}
	mem_info.name = mem_name;
	mv_sys_dma_mem_get_info(&mem_info);
	/* the emulated-BAR offsets table MUST be the first thing allocated on the dma-able memory */
	if (mv_sys_dma_mem_virt2phys(nmp->emul_bars_mem) != mem_info.paddr) {
		pr_err("failed to allocate the NMP emulated-BAR offsets table");
		pr_err(" (the table MUST be the first thing allocated on the dma-able memory)!\n");
		return -ENOMEM;
	}

	return 0;
}

static int dev_mng_mqa_init(struct nmp *nmp)
{
	int ret;
	struct mqa_params params;

	pr_debug("Initializing MQA\n");

	params.num_qs = MQA_QUEUE_MAX;

	/* Initializing MQA tables */
	ret = mqa_init(&params, &(nmp->mqa));
	if (ret) {
		pr_err("Failed to initialize MQA tables\n");
		return ret;
	}

	pr_debug("Initializing MQA %p %p\n", nmp->mqa, nmp->mqa);

	return 0;
}

/* Initialize the GIU instance */
static int dev_mng_init_giu(struct nmp *nmp, struct nmp_params *params)
{
	struct sys_iomem_params iomem_params;
	struct giu_params giu_params;
	int ret, i, j;

	/* Initialize the management GIU instance */
	pr_debug("Initializing GIU devices\n");

	iomem_params.type = SYS_IOMEM_T_MMAP;

	for (i = 0; i < ARRAY_SIZE(nmp_msi_map_names); i++) {
		iomem_params.devname = nmp_msi_map_names[i];
		iomem_params.index = nmp_msi_map_indxs[i];

		ret = sys_iomem_init(&iomem_params, &nmp->msi_iomem);
		if (!ret)
			break;
	}
	if (i == ARRAY_SIZE(nmp_msi_map_names)) {
		pr_err("no MSI iomap found!\n");
		return -EFAULT;
	}

	/* Map the MSI-X registers */
	ret = sys_iomem_map(nmp->msi_iomem, NMP_MSI_MAP_REGS_INDX,
			(phys_addr_t *)&nmp->msi_regs.phys_addr,
			&nmp->msi_regs.virt_addr);
	if (ret) {
		pr_err("Failed to map msi!\n");
		sys_iomem_deinit(nmp->msi_iomem);
		return ret;
	}

	pr_debug("MSI-X regs mapped at virt:%p phys:%p\n",
		nmp->msi_regs.virt_addr, nmp->msi_regs.phys_addr);

	memset(&giu_params, 0, sizeof(giu_params));
	giu_params.mqa = nmp->mqa;
	giu_params.msi_regs_pa = (u64)nmp->msi_regs.phys_addr + nmp_msi_map_regs_offs[i];
	giu_params.msi_regs_va = (u64)nmp->msi_regs.virt_addr + nmp_msi_map_regs_offs[i];

	/* pass dma engines params from nmp */
	giu_params.num_gie_types = params->giu_eng_params.num_giu_engines;
	for (i = 0; i < params->giu_eng_params.num_giu_engines; i++) {
		struct nmp_giu_eng_type_params *eng_type_params =
			&params->giu_eng_params.eng_type_params[i];

		giu_params.gie_type_params[i].num_dma_engines = eng_type_params->num_dma_engines;
		for (j = 0; j < eng_type_params->num_dma_engines; j++)
			giu_params.gie_type_params[i].engine_name[j] = eng_type_params->engine_name[j];
	}

	ret = giu_init(&giu_params, &nmp->giu);
	if (ret) {
		pr_err("Failed to initialize GIU!\n");
		sys_iomem_unmap(nmp->msi_iomem, NMP_MSI_MAP_REGS_INDX);
		sys_iomem_deinit(nmp->msi_iomem);
		return -ENODEV;
	}

	return 0;
}

static int dev_mng_hw_init(struct nmp *nmp, struct nmp_params *params)
{
	int ret;

	pr_debug("Initializing Device hardware\n");

	/* first thing, allocate memory for all emulated BARs */
	ret = dev_mng_alloc_emul_pci_bars(nmp);
	if (ret)
		return ret;

	/* Initialize MQA - device queue management */
	ret = dev_mng_mqa_init(nmp);
	if (ret)
		return ret;

	if (nmp->nmpp2.pp2_en) {
		ret = dev_mng_pp2_init(nmp);
		if (ret)
			return ret;
	}

	ret = dev_mng_init_giu(nmp, params);
	if (ret)
		return ret;

	return 0;
}

/** Software Functionality **/
/** ====================== **/

static int dev_mng_sw_init(struct nmp *nmp, struct nmp_params *params)
{
	int ret;
	struct nmdisp_params nmdisp_params;
	struct lf_mng_params lf_mng_params;

	pr_debug("Initializing Device software\n");

	/* Initialize Dispatcher */
	memset(&nmdisp_params, 0, sizeof(nmdisp_params));
	/* nothing to initialize in nmdisp_params */
	ret = nmdisp_init(&nmdisp_params, &(nmp->nmdisp));
	if (ret)
		return ret;

	memset(&lf_mng_params, 0, sizeof(lf_mng_params));
	lf_mng_params.nmp = nmp;
	lf_mng_params.nmdisp = nmp->nmdisp;
	lf_mng_params.mqa = nmp->mqa;
	lf_mng_params.giu = nmp->giu;
	lf_mng_params.num_containers = params->num_containers;
	lf_mng_params.containers_params = params->containers_params;
	ret = lf_mng_init(&lf_mng_params, &nmp->lf_mng);
	if (ret)
		return ret;

	return 0;
}

/** ======================== **/
/** == Device Termination == **/
/** ======================== **/

/** Hardware Functionality **/
/** ====================== **/

static int dev_mng_terminate_giu(struct nmp *nmp)
{
	if (nmp->giu)
		giu_deinit(nmp->giu);

	sys_iomem_unmap(nmp->msi_iomem, NMP_MSI_MAP_REGS_INDX);
	sys_iomem_deinit(nmp->msi_iomem);

	return 0;
}

static int dev_mng_mqa_terminate(struct nmp *nmp)
{
	int ret;

	pr_debug("Terminating MQA\n");

	/* Terminating MQA tables */
	ret = mqa_deinit(nmp->mqa);
	if (ret) {
		pr_err("Failed to terminate MQA\n");
		return ret;
	}

	return 0;
}

static int dev_mng_hw_terminate(struct nmp *nmp)
{
	int ret;

	pr_debug("Terminating Device Manager Init\n");

	/* terminate PP2 */
	ret = dev_mng_pp2_terminate(nmp);
	if (ret)
		return ret;

	/* TODO - for now just close the GIU instances */
	ret = dev_mng_terminate_giu(nmp);
	if (ret)
		return ret;

	/* Terminate MQA */
	ret = dev_mng_mqa_terminate(nmp);
	if (ret)
		return ret;

	mv_sys_dma_mem_free(nmp->emul_bars_mem);

	return 0;
}

/** Software Functionality **/
/** ====================== **/

static int dev_mng_sw_terminate(struct nmp *nmp)
{
	if (!nmp)
		return -EFAULT;

	nmdisp_deinit(nmp->nmdisp);

	/* Terminate topology */
	lf_mng_deinit(nmp->lf_mng);

	return 0;
}


/** Device Initialization **/
int dev_mng_init(struct nmp *nmp, struct nmp_params *params)
{
	int ret;

	pr_debug("Starting Device Manager Init\n");

	ret = dev_mng_hw_init(nmp, params);
	if (ret)
		return ret;

	ret = dev_mng_sw_init(nmp, params);
	if (ret)
		return ret;

	pr_debug("Completed Device Manager init\n");
	return 0;
}

/** Device termination **/
int dev_mng_terminate(struct nmp *nmp)
{
	int ret;

	pr_debug("Terminating Device Manager Init\n");

	ret = dev_mng_sw_terminate(nmp);
	if (ret)
		return ret;

	ret = dev_mng_hw_terminate(nmp);
	if (ret)
		return ret;

	return 0;
}

int dev_mng_get_free_bar(struct nmp *nmp, void **va, dma_addr_t *pa)
{
	int i;

	if (!nmp) {
		pr_err("np NMP obj!\n");
		return -EINVAL;
	}

	for (i = 0; i < NMP_PCI_MAX_NUM_BARS; i++)
		if (nmp->emul_bars_avail_tbl[i] == 0) {
			nmp->emul_bars_avail_tbl[i] = 1;
			break;
		}

	if (i == NMP_PCI_MAX_NUM_BARS) {
		/* All entries are occupied, not likely to happen. */
		pr_err("All bar-offsets table are occupied (%d entries).\n",
			NMP_PCI_MAX_NUM_BARS);

		return -ENODEV;
	}

	*va = nmp->emul_bars_mem + i * PCI_BAR0_ALLOC_SIZE;
	/* Get the relevant physical address. */
	*pa = mv_sys_dma_mem_virt2phys(*va);

	return i;
}

void dev_mng_put_bar(struct nmp *nmp, int index)
{
	if (!nmp) {
		pr_err("np NMP obj!\n");
		return;
	}

	if (index == NMP_PCI_MAX_NUM_BARS) {
		pr_err("index out of range!\n");
		return;
	}

	nmp->emul_bars_avail_tbl[index] = 0;
}
